package org.budo.dubbo.protocol.http.page.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.budo.dubbo.protocol.http.BudoDubboHttpProtocol;
import org.budo.dubbo.protocol.http.authentication.AuthenticationCheckUtil;
import org.budo.dubbo.protocol.http.page.AbstractPage;
import org.budo.dubbo.protocol.http.page.AbstractPage.Scope;
import org.budo.dubbo.protocol.http.page.entity.FunctionEntity;
import org.budo.dubbo.protocol.http.page.entity.InterfaceEntity;
import org.budo.dubbo.protocol.http.sign.SignCheck;
import org.budo.dubbo.protocol.http.util.BudoReflectionUtil;
import org.budo.support.lang.util.MapUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.slf4j.Slf4j;
import org.budo.support.spring.context.aware.BudoApplicationContextAware;
import org.budo.support.spring.util.SpringUtil;
import org.budo.support.spring.web.util.RequestMappingUtil;
import org.slf4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.RequestMapping;

import com.alibaba.dubbo.common.utils.CollectionUtils;
import com.alibaba.dubbo.rpc.Exporter;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;

/**
 * 接口结构
 * 
 * @author lmw
 */
public class InterfaceStructureUtil {
    private static final Logger log = Slf4j.getLogger();

    public static List<InterfaceEntity> listInterfaceMap(Scope scope) {
        if (Scope.Controller == scope) {
            return listControllerEntity();
        }

        List<InterfaceEntity> interfaces = new ArrayList<InterfaceEntity>();

        BudoDubboHttpProtocol budoDubboHttpProtocol = BudoDubboHttpProtocol.getInstance();
        if (null == budoDubboHttpProtocol) {
            throw new IllegalStateException("#54 budoDubboHttpProtocol#getInstance is null, classLoader=" + InterfaceStructureUtil.class.getClassLoader());
        }

        Map<String, Exporter<?>> exporterMap = budoDubboHttpProtocol.getExporterMap();
        Set<String> exporterKeys = exporterMap.keySet();
        for (String typeName : exporterKeys) {
            InterfaceEntity interfaceMap = interfaceMapByType(typeName);
            Object interfaceHiddenObj = interfaceMap.get("hidden");
            Boolean interfaceHidden = null == interfaceHiddenObj ? false : "true".equalsIgnoreCase(interfaceHiddenObj.toString());
            if (Scope.All == scope //
                    || (Scope.Private == scope && interfaceHidden.equals(true))//
                    || (Scope.Public == scope && interfaceHidden.equals(false))) {
                interfaces.add(interfaceMap);
            }
        }
        return interfaces;
    }

    /**
     * @see #interfaceMapByType(String)
     */
    public static Map<String, Object> controllerMapByType(String controllerTypeName) {
        BudoApplicationContextAware contextAware = BudoApplicationContextAware.getInstance();
        if (null == contextAware) {
            log.warn("#69 BudoApplicationContextAware#getInstance is null");
            return null;
        }

        ApplicationContext applicationContext = contextAware.getApplicationContext();
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            Object bean = SpringUtil.getBean(applicationContext, beanDefinitionName);
            if (null == bean) {
                continue;
            }

            if (!hasRequestMapping(bean)) {
                continue;
            }

            Class beanType = bean.getClass();
            while (beanType.getName().contains("EnhancerByCGLIB") || beanType.getName().contains("EnhancerBySpringCGLIB")) {
                beanType = beanType.getSuperclass();
            }
            if (!beanType.getName().endsWith(controllerTypeName)) {
                continue;
            }

            Map<String, Object> controllerMap = controllerMapOfBean(bean);
            return controllerMap;
        }

        return null;
    }

    public static List<InterfaceEntity> listControllerEntity() {
        List<InterfaceEntity> controllers = new ArrayList<InterfaceEntity>();
        BudoApplicationContextAware contextAware = BudoApplicationContextAware.getInstance();
        if (null == contextAware) {
            log.warn("#69 BudoApplicationContextAware#getInstance is null");
        }

        ApplicationContext applicationContext = contextAware.getApplicationContext();
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            Object bean = SpringUtil.getBean(applicationContext, beanDefinitionName);
            if (null == bean) {
                continue;
            }
            if (!hasRequestMapping(bean)) {
                continue;
            }

            InterfaceEntity controllerMap = controllerMapOfBean(bean);
            controllers.add(controllerMap);
        }

        return controllers;
    }

    public static List<FunctionEntity> requestMappingFunctions(Class controllerType) {
        Method[] functions = controllerType.getMethods();

        List<FunctionEntity> functionMapList = new ArrayList<FunctionEntity>();

        for (Method method : functions) {
            RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
            if (null == annotation) {
                continue;
            }

            FunctionEntity functionMap = interfaceFunctionMap(controllerType, method);

            functionMapList.add(functionMap);
        }

        return functionMapList;
    }

    /**
     * @see #controllerMapByType(String)
     */
    private static InterfaceEntity controllerMapOfBean(Object bean) {
        InterfaceEntity controllerMap = new InterfaceEntity();

        Class beanType = bean.getClass();
        while (beanType.getName().contains("EnhancerByCGLIB") || beanType.getName().contains("EnhancerBySpringCGLIB")) {
            beanType = beanType.getSuperclass();
        }

        controllerMap.put("typeName", beanType.getName());
        controllerMap.put("name", beanType.getSimpleName());
        controllerMap.put("simpleName", beanType.getSimpleName());
        controllerMap.put("fullName", beanType.getName());

        if (AbstractPage.HAS_SWAGGER) {
            Api api = (Api) beanType.getAnnotation(Api.class);
            if (null != api) {
                String description = api.value();
                if (!StringUtil.isEmpty(description)) {
                    controllerMap.put("description", description);
                }
            }
        }

        List<Map<String, Object>> controllerMethodMapList = new ArrayList<Map<String, Object>>();
        Method[] typeMethods = beanType.getMethods();
        for (Method method : typeMethods) {
            if (method.getDeclaringClass().equals(Object.class)) {
                continue;
            }

            RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
            if (null == annotation) {
                continue;
            }

            String description = "";
            Map<String, Object> controllerMethodMap = MapUtil.stringObjectMap("name", method.getName());
            if (AbstractPage.HAS_SWAGGER) {
                ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
                if (null != apiOperation) {
                    description += apiOperation.value();
                }
            }

            List<String> requestMappings = RequestMappingUtil.getRequestMapping(method);
            if (null != requestMappings) {
                description = StringUtil.join(requestMappings) + " " + description;
            }

            controllerMethodMap.put("description", description);
            controllerMethodMapList.add(controllerMethodMap);
        }

        controllerMap.put("methods", controllerMethodMapList);
        return controllerMap;
    }

    private static Boolean hasRequestMapping(Object bean) {
        List<Method> methods = ReflectUtil.getAllMethods(bean.getClass());
        for (Method method : methods) {
            RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
            if (null != annotation) {
                return true;
            }
        }

        return false;
    }

    public static List<Map<String, Object>> interfaceFunctions(Class interfaceType) {
        Method[] functions = interfaceType.getMethods();

        List<Map<String, Object>> functionMapList = new ArrayList<Map<String, Object>>();

        for (Method method : functions) {
            Map<String, Object> functionMap = interfaceFunctionMap(interfaceType, method);

            functionMapList.add(functionMap);
        }

        return functionMapList;
    }

    /**
     * @see #controllerMapByType(String)
     */
    public static InterfaceEntity interfaceMapByType(String typeName) {
        InterfaceEntity interfaceMap = new InterfaceEntity();

        Class interfaceType = ReflectUtil.classForName(typeName);

        interfaceMap.put("name", interfaceType.getSimpleName());
        interfaceMap.put("simpleName", interfaceType.getSimpleName());
        interfaceMap.put("fullName", interfaceType.getName());

        if (AbstractPage.HAS_SWAGGER) {
            Api api = (Api) interfaceType.getAnnotation(Api.class);
            if (null != api) {
                interfaceMap.put("hidden", api.hidden());

                String description = api.value();
                if (!StringUtil.isEmpty(description)) {
                    interfaceMap.put("description", description);
                }
            }
        }

        List<Map<String, Object>> methods = new ArrayList<Map<String, Object>>();

        Method[] typeMethods = interfaceType.getMethods();
        for (Method method : typeMethods) {
            if (method.getDeclaringClass().equals(Object.class)) {
                continue;
            }

            Map<String, Object> methodMap = interfaceFunctionMap(interfaceType, method);

            methods.add(methodMap);
        }

        interfaceMap.put("methods", methods);
        return interfaceMap;
    }

    public static FunctionEntity interfaceFunctionMap(Class matchedInterfaceType, Method method) {
        String[] parameterTypeNames = GenericTypeHtmlUtil.typeLinks(method.getGenericParameterTypes());

        SignCheck signCheckAnnotation = BudoReflectionUtil.getMethodAnnotation(matchedInterfaceType, method.getName(), method.getParameterTypes(), SignCheck.class);

        int len = null == parameterTypeNames ? 0 : parameterTypeNames.length;

        Boolean[] nullAble = new Boolean[len];
        String[] parameterDescription = new String[len];

        Annotation[][] parametersAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < len; i++) {
            nullAble[i] = true;

            Annotation[] parameterAnnotations = parametersAnnotations[i];
            for (Annotation parameterAnnotation : parameterAnnotations) {
                String parameterAnnotationTypeName = parameterAnnotation.annotationType().getName();
                if (parameterAnnotationTypeName.equals("javax.validation.constraints.NotNull") //
                        || parameterAnnotationTypeName.equals("org.hibernate.validator.constraints.NotEmpty")) {
                    nullAble[i] = false;
                }
                if (parameterAnnotation.annotationType().getName().equals("io.swagger.annotations.ApiParam")) {
                    ApiParam apiParam = (ApiParam) parameterAnnotation;
                    parameterDescription[i] = apiParam.value();
                }
            }
        }

        String returnTypeName = GenericTypeHtmlUtil.typeLink(method.getGenericReturnType());

        Boolean[] parameterTypeIsEnum = GenericTypeHtmlUtil.parameterTypeIsEnum(method);

        Boolean deprecated = null != method.getAnnotation(Deprecated.class);

        Class interfaceType = matchedInterfaceType;
        while (interfaceType.getName().contains("$$EnhancerByCGLIB$$")) {
            interfaceType = interfaceType.getSuperclass();
        }
        String interfaceName = interfaceType.getSimpleName();

        String[] parameterNames = BudoReflectionUtil.getParameterNamesCached(matchedInterfaceType, method);

        Map<String, Object> dataMap = CollectionUtils.toMap("interfaceName", interfaceName, //
                "deprecated", deprecated, //
                "functionName", method.getName(), //
                "name", method.getName(), //
                "method", method, //
                "nullAble", nullAble, //
                "parameterDescription", parameterDescription, //
                "parameterNames", parameterNames, //
                "parameterTypeNames", parameterTypeNames, //
                "parameterTypeIsEnum", parameterTypeIsEnum, //
                "returnTypeName", returnTypeName, //
                "signCheck", (null != signCheckAnnotation && signCheckAnnotation.value()), //
                "authenticationCheck", AuthenticationCheckUtil.requireAuthenticationCheck_aop(matchedInterfaceType, method));

        String description = "";
        if (AbstractPage.HAS_SWAGGER) {
            ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
            if (null != apiOperation) {
                description += apiOperation.value();
            }
        }

        List<String> requestMappings = RequestMappingUtil.getRequestMapping(method);
        if (null != requestMappings) {
            description = StringUtil.join(requestMappings) + " " + description;
        }

        dataMap.put("description", description);
        return new FunctionEntity(dataMap);
    }
}